{
 "cells": [
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T03:09:34.444644Z",
     "start_time": "2025-01-17T03:09:34.440225Z"
    }
   },
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "    \n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cpu\n",
      "cpu\n"
     ]
    }
   ],
   "execution_count": 8
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 准备数据"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T03:09:34.488978Z",
     "start_time": "2025-01-17T03:09:34.481479Z"
    }
   },
   "source": [
    "from sklearn.datasets import fetch_california_housing\n",
    "\n",
    "housing = fetch_california_housing(data_home='data')\n",
    "print(housing.DESCR)\n",
    "print(housing.data.shape)\n",
    "print(housing.target.shape)"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      ".. _california_housing_dataset:\n",
      "\n",
      "California Housing dataset\n",
      "--------------------------\n",
      "\n",
      "**Data Set Characteristics:**\n",
      "\n",
      ":Number of Instances: 20640\n",
      "\n",
      ":Number of Attributes: 8 numeric, predictive attributes and the target\n",
      "\n",
      ":Attribute Information:\n",
      "    - MedInc        median income in block group\n",
      "    - HouseAge      median house age in block group\n",
      "    - AveRooms      average number of rooms per household\n",
      "    - AveBedrms     average number of bedrooms per household\n",
      "    - Population    block group population\n",
      "    - AveOccup      average number of household members\n",
      "    - Latitude      block group latitude\n",
      "    - Longitude     block group longitude\n",
      "\n",
      ":Missing Attribute Values: None\n",
      "\n",
      "This dataset was obtained from the StatLib repository.\n",
      "https://www.dcc.fc.up.pt/~ltorgo/Regression/cal_housing.html\n",
      "\n",
      "The target variable is the median house value for California districts,\n",
      "expressed in hundreds of thousands of dollars ($100,000).\n",
      "\n",
      "This dataset was derived from the 1990 U.S. census, using one row per census\n",
      "block group. A block group is the smallest geographical unit for which the U.S.\n",
      "Census Bureau publishes sample data (a block group typically has a population\n",
      "of 600 to 3,000 people).\n",
      "\n",
      "A household is a group of people residing within a home. Since the average\n",
      "number of rooms and bedrooms in this dataset are provided per household, these\n",
      "columns may take surprisingly large values for block groups with few households\n",
      "and many empty houses, such as vacation resorts.\n",
      "\n",
      "It can be downloaded/loaded using the\n",
      ":func:`sklearn.datasets.fetch_california_housing` function.\n",
      "\n",
      ".. rubric:: References\n",
      "\n",
      "- Pace, R. Kelley and Ronald Barry, Sparse Spatial Autoregressions,\n",
      "  Statistics and Probability Letters, 33 (1997) 291-297\n",
      "\n",
      "(20640, 8)\n",
      "(20640,)\n"
     ]
    }
   ],
   "execution_count": 9
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T03:09:34.492238Z",
     "start_time": "2025-01-17T03:09:34.488978Z"
    }
   },
   "source": [
    "# print(housing.data[0:5])\n",
    "import pprint  #打印的格式比较 好看\n",
    "\n",
    "pprint.pprint(housing.data[0:5])\n",
    "print('-'*50)\n",
    "pprint.pprint(housing.target[0:5])"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "array([[ 8.32520000e+00,  4.10000000e+01,  6.98412698e+00,\n",
      "         1.02380952e+00,  3.22000000e+02,  2.55555556e+00,\n",
      "         3.78800000e+01, -1.22230000e+02],\n",
      "       [ 8.30140000e+00,  2.10000000e+01,  6.23813708e+00,\n",
      "         9.71880492e-01,  2.40100000e+03,  2.10984183e+00,\n",
      "         3.78600000e+01, -1.22220000e+02],\n",
      "       [ 7.25740000e+00,  5.20000000e+01,  8.28813559e+00,\n",
      "         1.07344633e+00,  4.96000000e+02,  2.80225989e+00,\n",
      "         3.78500000e+01, -1.22240000e+02],\n",
      "       [ 5.64310000e+00,  5.20000000e+01,  5.81735160e+00,\n",
      "         1.07305936e+00,  5.58000000e+02,  2.54794521e+00,\n",
      "         3.78500000e+01, -1.22250000e+02],\n",
      "       [ 3.84620000e+00,  5.20000000e+01,  6.28185328e+00,\n",
      "         1.08108108e+00,  5.65000000e+02,  2.18146718e+00,\n",
      "         3.78500000e+01, -1.22250000e+02]])\n",
      "--------------------------------------------------\n",
      "array([4.526, 3.585, 3.521, 3.413, 3.422])\n"
     ]
    }
   ],
   "execution_count": 10
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T03:09:34.501111Z",
     "start_time": "2025-01-17T03:09:34.495746Z"
    }
   },
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "#拆分训练集和测试集，random_state是随机种子,同样的随机数种子，是为了得到同样的随机值\n",
    "x_train_all, x_test, y_train_all, y_test = train_test_split(\n",
    "    housing.data, housing.target, random_state = 7)\n",
    "x_train, x_valid, y_train, y_valid = train_test_split(\n",
    "    x_train_all, y_train_all, random_state = 11)\n",
    "# 训练集\n",
    "print(x_train.shape, y_train.shape)\n",
    "# 验证集\n",
    "print(x_valid.shape, y_valid.shape)\n",
    "# 测试集\n",
    "print(x_test.shape, y_test.shape)\n",
    "\n",
    "dataset_maps = {\n",
    "    \"train\": [x_train, y_train],\n",
    "    \"valid\": [x_valid, y_valid],\n",
    "    \"test\": [x_test, y_test],\n",
    "}\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(11610, 8) (11610,)\n",
      "(3870, 8) (3870,)\n",
      "(5160, 8) (5160,)\n"
     ]
    }
   ],
   "execution_count": 11
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T03:09:34.506115Z",
     "start_time": "2025-01-17T03:09:34.501111Z"
    }
   },
   "source": [
    "from sklearn.preprocessing import StandardScaler\n",
    "from torch.utils.data import DataLoader\n",
    "\n",
    "\n",
    "scaler = StandardScaler()\n",
    "scaler.fit(x_train)"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "StandardScaler()"
      ],
      "text/html": [
       "<style>#sk-container-id-2 {\n",
       "  /* Definition of color scheme common for light and dark mode */\n",
       "  --sklearn-color-text: #000;\n",
       "  --sklearn-color-text-muted: #666;\n",
       "  --sklearn-color-line: gray;\n",
       "  /* Definition of color scheme for unfitted estimators */\n",
       "  --sklearn-color-unfitted-level-0: #fff5e6;\n",
       "  --sklearn-color-unfitted-level-1: #f6e4d2;\n",
       "  --sklearn-color-unfitted-level-2: #ffe0b3;\n",
       "  --sklearn-color-unfitted-level-3: chocolate;\n",
       "  /* Definition of color scheme for fitted estimators */\n",
       "  --sklearn-color-fitted-level-0: #f0f8ff;\n",
       "  --sklearn-color-fitted-level-1: #d4ebff;\n",
       "  --sklearn-color-fitted-level-2: #b3dbfd;\n",
       "  --sklearn-color-fitted-level-3: cornflowerblue;\n",
       "\n",
       "  /* Specific color for light theme */\n",
       "  --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, white)));\n",
       "  --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-icon: #696969;\n",
       "\n",
       "  @media (prefers-color-scheme: dark) {\n",
       "    /* Redefinition of color scheme for dark theme */\n",
       "    --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, #111)));\n",
       "    --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-icon: #878787;\n",
       "  }\n",
       "}\n",
       "\n",
       "#sk-container-id-2 {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 pre {\n",
       "  padding: 0;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 input.sk-hidden--visually {\n",
       "  border: 0;\n",
       "  clip: rect(1px 1px 1px 1px);\n",
       "  clip: rect(1px, 1px, 1px, 1px);\n",
       "  height: 1px;\n",
       "  margin: -1px;\n",
       "  overflow: hidden;\n",
       "  padding: 0;\n",
       "  position: absolute;\n",
       "  width: 1px;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-dashed-wrapped {\n",
       "  border: 1px dashed var(--sklearn-color-line);\n",
       "  margin: 0 0.4em 0.5em 0.4em;\n",
       "  box-sizing: border-box;\n",
       "  padding-bottom: 0.4em;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-container {\n",
       "  /* jupyter's `normalize.less` sets `[hidden] { display: none; }`\n",
       "     but bootstrap.min.css set `[hidden] { display: none !important; }`\n",
       "     so we also need the `!important` here to be able to override the\n",
       "     default hidden behavior on the sphinx rendered scikit-learn.org.\n",
       "     See: https://github.com/scikit-learn/scikit-learn/issues/21755 */\n",
       "  display: inline-block !important;\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-text-repr-fallback {\n",
       "  display: none;\n",
       "}\n",
       "\n",
       "div.sk-parallel-item,\n",
       "div.sk-serial,\n",
       "div.sk-item {\n",
       "  /* draw centered vertical line to link estimators */\n",
       "  background-image: linear-gradient(var(--sklearn-color-text-on-default-background), var(--sklearn-color-text-on-default-background));\n",
       "  background-size: 2px 100%;\n",
       "  background-repeat: no-repeat;\n",
       "  background-position: center center;\n",
       "}\n",
       "\n",
       "/* Parallel-specific style estimator block */\n",
       "\n",
       "#sk-container-id-2 div.sk-parallel-item::after {\n",
       "  content: \"\";\n",
       "  width: 100%;\n",
       "  border-bottom: 2px solid var(--sklearn-color-text-on-default-background);\n",
       "  flex-grow: 1;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-parallel {\n",
       "  display: flex;\n",
       "  align-items: stretch;\n",
       "  justify-content: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-parallel-item {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-parallel-item:first-child::after {\n",
       "  align-self: flex-end;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-parallel-item:last-child::after {\n",
       "  align-self: flex-start;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-parallel-item:only-child::after {\n",
       "  width: 0;\n",
       "}\n",
       "\n",
       "/* Serial-specific style estimator block */\n",
       "\n",
       "#sk-container-id-2 div.sk-serial {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "  align-items: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  padding-right: 1em;\n",
       "  padding-left: 1em;\n",
       "}\n",
       "\n",
       "\n",
       "/* Toggleable style: style used for estimator/Pipeline/ColumnTransformer box that is\n",
       "clickable and can be expanded/collapsed.\n",
       "- Pipeline and ColumnTransformer use this feature and define the default style\n",
       "- Estimators will overwrite some part of the style using the `sk-estimator` class\n",
       "*/\n",
       "\n",
       "/* Pipeline and ColumnTransformer style (default) */\n",
       "\n",
       "#sk-container-id-2 div.sk-toggleable {\n",
       "  /* Default theme specific background. It is overwritten whether we have a\n",
       "  specific estimator or a Pipeline/ColumnTransformer */\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "/* Toggleable label */\n",
       "#sk-container-id-2 label.sk-toggleable__label {\n",
       "  cursor: pointer;\n",
       "  display: flex;\n",
       "  width: 100%;\n",
       "  margin-bottom: 0;\n",
       "  padding: 0.5em;\n",
       "  box-sizing: border-box;\n",
       "  text-align: center;\n",
       "  align-items: start;\n",
       "  justify-content: space-between;\n",
       "  gap: 0.5em;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 label.sk-toggleable__label .caption {\n",
       "  font-size: 0.6rem;\n",
       "  font-weight: lighter;\n",
       "  color: var(--sklearn-color-text-muted);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 label.sk-toggleable__label-arrow:before {\n",
       "  /* Arrow on the left of the label */\n",
       "  content: \"▸\";\n",
       "  float: left;\n",
       "  margin-right: 0.25em;\n",
       "  color: var(--sklearn-color-icon);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 label.sk-toggleable__label-arrow:hover:before {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "/* Toggleable content - dropdown */\n",
       "\n",
       "#sk-container-id-2 div.sk-toggleable__content {\n",
       "  max-height: 0;\n",
       "  max-width: 0;\n",
       "  overflow: hidden;\n",
       "  text-align: left;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-toggleable__content.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-toggleable__content pre {\n",
       "  margin: 0.2em;\n",
       "  border-radius: 0.25em;\n",
       "  color: var(--sklearn-color-text);\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-toggleable__content.fitted pre {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 input.sk-toggleable__control:checked~div.sk-toggleable__content {\n",
       "  /* Expand drop-down */\n",
       "  max-height: 200px;\n",
       "  max-width: 100%;\n",
       "  overflow: auto;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {\n",
       "  content: \"▾\";\n",
       "}\n",
       "\n",
       "/* Pipeline/ColumnTransformer-specific style */\n",
       "\n",
       "#sk-container-id-2 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-label.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator-specific style */\n",
       "\n",
       "/* Colorize estimator box */\n",
       "#sk-container-id-2 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-estimator.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-label label.sk-toggleable__label,\n",
       "#sk-container-id-2 div.sk-label label {\n",
       "  /* The background is the default theme color */\n",
       "  color: var(--sklearn-color-text-on-default-background);\n",
       "}\n",
       "\n",
       "/* On hover, darken the color of the background */\n",
       "#sk-container-id-2 div.sk-label:hover label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "/* Label box, darken color on hover, fitted */\n",
       "#sk-container-id-2 div.sk-label.fitted:hover label.sk-toggleable__label.fitted {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator label */\n",
       "\n",
       "#sk-container-id-2 div.sk-label label {\n",
       "  font-family: monospace;\n",
       "  font-weight: bold;\n",
       "  display: inline-block;\n",
       "  line-height: 1.2em;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-label-container {\n",
       "  text-align: center;\n",
       "}\n",
       "\n",
       "/* Estimator-specific */\n",
       "#sk-container-id-2 div.sk-estimator {\n",
       "  font-family: monospace;\n",
       "  border: 1px dotted var(--sklearn-color-border-box);\n",
       "  border-radius: 0.25em;\n",
       "  box-sizing: border-box;\n",
       "  margin-bottom: 0.5em;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-estimator.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "/* on hover */\n",
       "#sk-container-id-2 div.sk-estimator:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-2 div.sk-estimator.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Specification for estimator info (e.g. \"i\" and \"?\") */\n",
       "\n",
       "/* Common style for \"i\" and \"?\" */\n",
       "\n",
       ".sk-estimator-doc-link,\n",
       "a:link.sk-estimator-doc-link,\n",
       "a:visited.sk-estimator-doc-link {\n",
       "  float: right;\n",
       "  font-size: smaller;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1em;\n",
       "  height: 1em;\n",
       "  width: 1em;\n",
       "  text-decoration: none !important;\n",
       "  margin-left: 0.5em;\n",
       "  text-align: center;\n",
       "  /* unfitted */\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted,\n",
       "a:link.sk-estimator-doc-link.fitted,\n",
       "a:visited.sk-estimator-doc-link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "div.sk-estimator:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "div.sk-estimator.fitted:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "/* Span, style for the box shown on hovering the info icon */\n",
       ".sk-estimator-doc-link span {\n",
       "  display: none;\n",
       "  z-index: 9999;\n",
       "  position: relative;\n",
       "  font-weight: normal;\n",
       "  right: .2ex;\n",
       "  padding: .5ex;\n",
       "  margin: .5ex;\n",
       "  width: min-content;\n",
       "  min-width: 20ex;\n",
       "  max-width: 50ex;\n",
       "  color: var(--sklearn-color-text);\n",
       "  box-shadow: 2pt 2pt 4pt #999;\n",
       "  /* unfitted */\n",
       "  background: var(--sklearn-color-unfitted-level-0);\n",
       "  border: .5pt solid var(--sklearn-color-unfitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted span {\n",
       "  /* fitted */\n",
       "  background: var(--sklearn-color-fitted-level-0);\n",
       "  border: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link:hover span {\n",
       "  display: block;\n",
       "}\n",
       "\n",
       "/* \"?\"-specific style due to the `<a>` HTML tag */\n",
       "\n",
       "#sk-container-id-2 a.estimator_doc_link {\n",
       "  float: right;\n",
       "  font-size: 1rem;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1rem;\n",
       "  height: 1rem;\n",
       "  width: 1rem;\n",
       "  text-decoration: none;\n",
       "  /* unfitted */\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 a.estimator_doc_link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "#sk-container-id-2 a.estimator_doc_link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "#sk-container-id-2 a.estimator_doc_link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "</style><div id=\"sk-container-id-2\" class=\"sk-top-container\"><div class=\"sk-text-repr-fallback\"><pre>StandardScaler()</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class=\"sk-container\" hidden><div class=\"sk-item\"><div class=\"sk-estimator fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-2\" type=\"checkbox\" checked><label for=\"sk-estimator-id-2\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow\"><div><div>StandardScaler</div></div><div><a class=\"sk-estimator-doc-link fitted\" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.6/modules/generated/sklearn.preprocessing.StandardScaler.html\">?<span>Documentation for StandardScaler</span></a><span class=\"sk-estimator-doc-link fitted\">i<span>Fitted</span></span></div></label><div class=\"sk-toggleable__content fitted\"><pre>StandardScaler()</pre></div> </div></div></div></div>"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 12
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 构建数据集"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T03:09:34.511609Z",
     "start_time": "2025-01-17T03:09:34.506115Z"
    }
   },
   "source": [
    "from torch.utils.data import Dataset\n",
    "\n",
    "class HousingDataset(Dataset):\n",
    "    def __init__(self, mode='train'):\n",
    "        self.x, self.y = dataset_maps[mode]\n",
    "        self.x = torch.from_numpy(scaler.transform(self.x)).float()\n",
    "        self.y = torch.from_numpy(self.y).float().reshape(-1, 1)\n",
    "            \n",
    "    def __len__(self):\n",
    "        return len(self.x)\n",
    "    \n",
    "    def __getitem__(self, idx):\n",
    "        return self.x[idx], self.y[idx]\n",
    "    \n",
    "    \n",
    "train_ds = HousingDataset(\"train\")\n",
    "valid_ds = HousingDataset(\"valid\")\n",
    "test_ds = HousingDataset(\"test\")"
   ],
   "outputs": [],
   "execution_count": 13
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T03:09:34.517012Z",
     "start_time": "2025-01-17T03:09:34.511609Z"
    }
   },
   "source": [
    "train_ds[1]"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(tensor([-0.2981,  0.3523, -0.1092, -0.2506, -0.0341, -0.0060,  1.0806, -1.0611]),\n",
       " tensor([1.5140]))"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 14
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### DataLoader"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T03:09:34.525510Z",
     "start_time": "2025-01-17T03:09:34.522016Z"
    }
   },
   "source": [
    "from torch.utils.data import DataLoader\n",
    "\n",
    "\n",
    "batch_size = 8  #过大会导致GPU内存溢出，过小会导致训练时间过长\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True)\n",
    "val_loader = DataLoader(valid_ds, batch_size=batch_size, shuffle=False)\n",
    "test_loader = DataLoader(test_ds, batch_size=batch_size, shuffle=False)"
   ],
   "outputs": [],
   "execution_count": 15
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定义模型"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T03:09:34.540773Z",
     "start_time": "2025-01-17T03:09:34.537068Z"
    }
   },
   "source": [
    "#回归模型我们只需要1个数\n",
    "\n",
    "class WideDeep(nn.Module):\n",
    "    def __init__(self, input_dim=8):\n",
    "        super().__init__()\n",
    "        self.deep = nn.Sequential(\n",
    "            nn.Linear(input_dim, 30), #30个神经元\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(30, 30), #30个神经元\n",
    "            nn.ReLU()\n",
    "            )\n",
    "        # pytorch 需要自行计算输出输出维度\n",
    "        self.output_layer = nn.Linear(30 + input_dim, 1)\n",
    "        \n",
    "        # 初始化权重\n",
    "        self.init_weights()\n",
    "        \n",
    "    def init_weights(self):\n",
    "        \"\"\"使用 xavier 均匀分布来初始化全连接层的权重 W\"\"\"\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, nn.Linear):\n",
    "                nn.init.xavier_uniform_(m.weight)\n",
    "                nn.init.zeros_(m.bias)\n",
    "        \n",
    "    def forward(self, x):\n",
    "        # x.shape [batch size, 8]\n",
    "        deep_output = self.deep(x)\n",
    "        # print(deep_output.shape)\n",
    "        # concat [batch size, 30] with x [batch size 8]，得到 [batch size, 38]\n",
    "        concat = torch.cat([x, deep_output], dim=1)\n",
    "        logits = self.output_layer(concat) # 输出层，输入维度是 38，输出维度是 1\n",
    "        # logits.shape [batch size, 1]\n",
    "        return logits"
   ],
   "outputs": [],
   "execution_count": 16
  },
  {
   "cell_type": "code",
   "source": [
    "# train_ds[0][0]\n",
    "#验证模型是否正确\n",
    "input=train_ds[0][0].reshape(1, -1)\n",
    "print(input.shape)\n",
    "model=WideDeep()\n",
    "out=model(input)\n",
    "out.shape"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2025-01-17T03:09:34.547601Z",
     "start_time": "2025-01-17T03:09:34.541776Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([1, 8])\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "torch.Size([1, 1])"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 17
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T03:09:34.550714Z",
     "start_time": "2025-01-17T03:09:34.547601Z"
    }
   },
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute \n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "        \n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter \n",
    "            self.counter = 0\n",
    "        else: \n",
    "            self.counter += 1\n",
    "            \n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ],
   "outputs": [],
   "execution_count": 18
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T03:09:34.580312Z",
     "start_time": "2025-01-17T03:09:34.577985Z"
    }
   },
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "        \n",
    "    return np.mean(loss_list)\n"
   ],
   "outputs": [],
   "execution_count": 19
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 训练"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T03:09:48.403198Z",
     "start_time": "2025-01-17T03:09:34.580312Z"
    }
   },
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=None,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "    \n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas)\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    " \n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "                \n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"step\": global_step\n",
    "                })\n",
    "                \n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "\n",
    "                    # 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(-val_loss)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "                    \n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "        \n",
    "    return record_dict\n",
    "        \n",
    "\n",
    "epoch = 10\n",
    "\n",
    "model = WideDeep()\n",
    "\n",
    "# 1. 定义损失函数 采用交叉熵损失\n",
    "loss_fct = nn.MSELoss()\n",
    "# 2. 定义优化器 采用SGD\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.0)\n",
    "\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=10, min_delta=1e-3)\n",
    "\n",
    "model = model.to(device)\n",
    "record = training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    "    )"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "  0%|          | 0/14520 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "89ae23d1d75249d09a279addfef113a3"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=2000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=2000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=2000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n"
     ]
    }
   ],
   "execution_count": 20
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T03:09:48.475492Z",
     "start_time": "2025-01-17T03:09:48.404203Z"
    }
   },
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        plt.plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        plt.plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        plt.grid()\n",
    "        plt.legend()\n",
    "        # plt.xticks(range(0, train_df.index[-1], 10*sample_step), range(0, train_df.index[-1], 10*sample_step))\n",
    "        plt.xlabel(\"step\")\n",
    "\n",
    "        plt.show()\n",
    "\n",
    "plot_learning_curves(record, sample_step=500)  #横坐标是 steps"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ],
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAhYAAAGwCAYAAAD16iy9AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAY+hJREFUeJzt3Qd8U1X7B/Bf0qa7ZW/K3nuKgCLKBhUXKiDi+DvxdaC+jvdVQcU9cOLrAgcIqICDWfbesqFsyt50j7TJ//OcJCUtXWkz7/19P59L0iQk96Rp7nPPec5zDFar1QoiIiIiNzC640mIiIiIBAMLIiIichsGFkREROQ2DCyIiIjIbRhYEBERkdswsCAiIiK3YWBBREREbhMML7NYLDhx4gSio6NhMBi8/fJERERUClL2Kjk5GTVr1oTRaPSfwEKCitjYWG+/LBEREbnB0aNHUbt2bf8JLKSnwrFjMTExbntes9mMBQsWoG/fvjCZTNALvbZbz21nu9luPdBru/257UlJSapjwHEc95vAwjH8IUGFuwOLiIgI9Zz+9IvwNL22W89tZ7vZbj3Qa7sDoe3FpTEweZOIiIjchoEFERERuQ0DCyIiInIbr+dYEBGR9kgpgaysLLfmGQQHByMjIwM5OTnQE7OP2i75HEFBQWV+HgYWRERUJhJQHDp0SAUX7qyZUL16dTWDUG81j6w+bHv58uXVa5fldRlYEBFRmQ6CJ0+eVGe6MhWxqMJJrpAgJSUlBVFRUW57zkBh8UHb5feYlpaGM2fOqJ9r1KhR6udiYEFERKWWnZ2tDkhSjVGmSLp7aCUsLEyXgUWWD9oeHh6uLiW4qFq1aqmHRfT12yIiIrdy5ACEhIT4elfIDRzBoeR5lBYDCyIiKjO95UFolcENv0cGFkREROQ2DCyIiIjIbRhYEBERlUG9evUwfvx4tzzX0qVLVdJkYmIiApVmZoWcT8nE2XQgK9sCP1yzhYiI/EjPnj3Rrl07twQEGzZsQGRkpFv2Sws0E1j0/3Q1LqUHo9u1aWhRK9TXu0NERAFM6jrIjBepgFmcKlWqeGWfAoVmhkIiQ23zbdOy9FX6lYjIn6hCS1nZbtnSs3Jcery8dkncd999WLZsGT755BM1C0K2SZMmqcu5c+eiY8eOCA0NxcqVK3HgwAEMHjwY1apVUwWrOnfujIULFxY5FGIwGPDtt9/i1ltvVdM3GzdujD///LPU7+nvv/+Oli1bqn2S1/rwww/z3P/ll1+q15C6F7Kfd9xxR+59v/32G1q3bq1qVFSqVAm9e/dGamoqPEkzPRYRIbbAIjUz29e7QkSkW+nmHLR4db5PXnvX6/0QEVL8YU0Cir1796JVq1Z4/fXX1W07d+5Uly+++CI++OADNGjQABUqVFBltQcOHIhx48apA/uPP/6Im266CfHx8ahTp06hrzF27Fi89957eP/99/HZZ59h+PDhOHLkCCpWrOhSmzZt2oQ777wTY8aMwV133YXVq1fj8ccfV0GCBEgbN27Ek08+iZ9++gndunXDhQsXsGLFCvV/pSLq0KFD1X5IkJOcnKzuK2kAVloaCixsTWGPBRERFaVcuXKqoJf0Jsi6GGLPnj3qUgKNPn365D5WAoG2bdvm/vzGG29g5syZqgfiiSeeKPQ17rvvPnVQF2+99RY+/fRTrF+/Hv3793dpXz/66CP06tULr7zyivq5SZMm2LVrlwpY5DUSEhJUfseNN96I6Oho1K1bF+3bt88NLKQy6m233aZuF9J74WnBWhsKSWVgQUTkM+GmINVz4I6y1slJyYiOiS5xWWt57bLq1KlTnp9lzQ7pLZg9e3bugTo9PV0d0IvSpk2b3Oty4I+Jicldh8MVu3fvVkMxzrp3766GXiQHRIIgCRqkh0WCFtkcQzASEElQIsFEv3790LdvXzVMIj0xfpNjIY2QqKl+/fpqvKZhw4YqevN0t0pJROb2WHAohIjIVyS/QHqQ3bGFhwS59Hh3VI3MP7vjueeeUz0U0usgwwhbtmxRB+rilog35ZueKPvmztVfHaSXYvPmzfjll1/UwmGvvvqqCiguXbqkpq3GxcWpvJEWLVqoIZmmTZuqlWj9JrB49913MWHCBHz++ecqipKfZexGdtZ/cizYY0FEREWToRDHOidFWbVqlRpykF4ACShk6OTw4cPwlubNm6t9yL9PMiTiWCRMZq5IUqYcj7dt26b2b/HixbkBjfRwSM7HP//8o9otgZLfDIVI0oh0yQwaNEj9LNmpEiXJuFFhMjMz1eaQlJSUu8BJWRY5yS/cZItUk9Oz3Pq8/s7RVj21We9tZ7vZbn8i+yW91nI27s4zckdPuOO53U2GD9atW4eDBw+q2R4yxCHyt6NRo0aYMWOGOu7JQVp6BOT+/PuV/2dLAe9HSd6j/M/5zDPPoEuXLir3Q5I416xZo07uZZPH/v3336oH4tprr1VDHHPmzFG3yywReawEGDJcIquVSnvPnj2rei0K2w9H2+T3mn9105J+Bl0KLCTj9Ouvv1bZtBItbd26VU3HkeSSwrz99tsqUspvwYIFbl1i98wJ6XwxYve+g5hj3g+9ke4uvdJr29luffHXdsvZspzFSy5CccMDpSEzGTzhkUceUbMrZGaI5Ex88cUXua/nnNMhxy9J0rzmmmtUIudTTz2FixcvqrY6TpTlYJyRkZH7s5DndP5ZDtb5H1MQWYLeQfZFApuJEyeqY+mbb76pppO+9NJLKiFTnkuGXH799VeVByIn8ZJrIVNdY2Nj1cyVJUuWqHwMeS65TdIXpAejsP2Qdsm+L1++PDfYKmjfimKwupAgIW/eyy+/rLpbJJKRbiSZgiONdKXHQhp37tw5lcziLp8s3IvPlx3GkA418datraAXEkHKF45EpPnH9LROr21nu9lufyIHS5mSKT3YUkfBXeTQJAdDySHQ28qpVh+2XX6fMpQix+n8v085fleuXFmVGy/q+O1Sj8X06dMxefJkTJkyRRXrkCSWp59+GjVr1sTIkSML/D8y71e2/OQPxJ1/JDERttfIUCW9/e+Pz9Pc/X4GEr22ne3WF39tt5xgysFPzvJLOnujJBxd9Y7n1hOLD9suryevW9DnraSfP5f2+Pnnn1fFQ+6++26VxDJixAg1/iNdNP6SvMk6FkRE5K8effRRldNR0Cb3aYFLPRYyvpI/epIhEU8k1riKlTeJiMjfvf7662oKa0HcmR4QMIGFlDGVnAopYypDITJ1RRI3H3jgAfhaZCgrbxIRkX+rWrWq2rTMpcBC6lVIgSzJpJUKYpJbIZm1Mv3G1yIdPRYMLIiIiAIjsJAMVZm24o71692NQyFERES+p5lUWy5CRkRE5HtGrS1CJoGFP6xdQkREpEfaCSzsQyHZFisys30/S4WIiEiPNBNYOC+Xy+EQIiLyJKk0WtJ8Q4PBgFmzZkEvNBNYBAcZYTLahkCYwElEROQbmgksRKi9NalZDCyIiIh8QVuBhX00JDWTQyFERD4hyfNZqe7ZzGmuPb6EifuySrfUYcpfNXrw4MGq4OOBAwfUdVlJVEptd+7cGQsXLnTbW7R9+3bccMMNCA8PR6VKlfDwww+r1WEdli5dil69eqkSD+XLl1erkR45ckTdJ6uKX3/99eo+qdTZsWNHbNy4EQFbxyJQAos09lgQEfmGBANv1XTLWW95V//TyyeAkMhiHzZkyBD861//UkuKywFcXLhwAfPmzcOcOXPUQX7gwIGq0rQsovnjjz+qytOyDLlUni6L1NRU9OvXD127dsWGDRtUscn/+7//U0uzT5o0SS1VLkuiy1pcU6dOVT+vX78+d5XT4cOHo3379pgwYYJaUkMWA/W3xek0GVgwx4KIiApToUIFDBgwQK3U7QgsfvvtN7UkuPQGyJpYbdu2zX38G2+8gZkzZ+LPP/9UAUBZTJkyRS1NLsFKZKQtCPr8889V4PLuu++qIEGWJe/fvz8aNmyo9qV58+a5/z8hIUEtCNqsWTP1c+PGjeFvtBVYqORNA4dCiIh8xRRh6zkoIxmmSEpORkx0dMmXDpfXLiE583/ooYfw5Zdfql6JyZMnq5W75bWkx2LMmDGYPXs2Tp48qXoN0tPT1UG9rHbv3q2CFkdQIWSoQ9orPSI9evTAyJEjcfvtt6N3797o06cP7rzzTtSoUUM9dvTo0aqH46efflL3S++LBCD+RJM5FhwKISLyEemyl+EId2wSKLjyePtwQUlID4EUU5Tg4ejRo1ixYoUKNoSsPio9FG+99Za6XYYbWrdujaysLHjD999/jwULFqBbt26YNm0amjRpgrVr16r7JODZuXMnBg0ahMWLF6NFixZqX/2JJgOLFPZYEBFREcLCwlQug/RU/PLLL2jatCk6dOig7lu1ahXuu+8+3HrrrSqgqF69Og4fPuyW123evLlKwJRcCwd5PekpkX1waNOmDV588UWsXr0arVq1UkMoDhJoPPPMMyr4kDZMnDgR/kRbgYW9NeyxICKi4kgPhfRYSA+Bo7fCkbcwY8YM1VMhQcCwYcOumEFSltcMCwtTwx07duxQCaSSSCrJmjIL5dChQ3j55ZdVwqbMBJHgYd++fSogkeEYyfGQWSNynwQkkgDqnIPhD7SVY8HppkREVEIy5bNixYoqt0GCB4ePPvpITTuVoQhJ6HzhhReQlJTklteMiIjA/Pnz8dRTT6lprPKz5FPIazru37NnD3744Qc1U0VyK0aNGoVHHnlE5XqcP38e9957L06fPq32TXosxo4dC3+iscDCNoeZPRZERFQcGX44ceJEgeW6JX/BmRzcnbkyNGLNV19DhlfyP7+D9FpIb4kEMlKnwjlxNSQkRA3b+DtNDYXY1yFDCqebEhER+YSmAouw3FkhHAohIiLPk+RPqc5Z0NayZUvoUbAm1wphjwUREXnBzTffjC5duhR4n8nPKmJ6S7AWh0K4CBkREXmDrNkhG2l2KMSevMlZIUREXpU/QZECkzum1Wpzuil7LIiIvEK6+2WBrLNnz6JKlSq5i2W54wAnlS5lXY0Sl/TWCIsP2i6Bobym/B7lNWUGSmlpKrAIyc2xYI8FEZE3yAqbtWvXxrFjx9xWndJxoJOCULK0uLuClUBh9WHbpY6GrOBaloAmWIuzQqTHQn4xevswEhH5gsyAkGqVZrPZbc8pz7V8+XK1KJfekiDNPmq7BInBwcFlPnZqcihEhvoyzBaEO7I5iYjI4wcl2dz5fFJpUspf6y2wCArwtmtq4Mrk1BoWySIiIvI+TQUWRgMQae+lYFlvIiIi79NUYCEi7IEFEziJiIi8T4OBhS1thFNOiYiIvE9zgUWkPYOTZb2JiIi8T7NDIVyIjIiIyPs0F1hE2odCOCuEiIjIzwOLevXqqcIZ+bdRo0bB73osGFgQERF5nUsFsjZs2ICcnMtDDDt27ECfPn0wZMgQ+IvIUEfyJodCiIiI/DqwkAVmnL3zzjto2LAhrrvuukL/T2ZmptockpKSckuWurv8qwgLtpUiTU7Pcuvz+ytHG/XQ1vz02na2m+3WA72225/bXtL9MVhLudatrIJWs2ZNjB49Gi+//HKhjxszZgzGjh17xe1TpkxRi524298JRsQdN6JHdQtur1/25V+JiIgISEtLw7Bhw5CYmIiYmBj3BxbTp09XL5CQkKACDFd6LGJjY3Hu3Lkid6w0kVRcXBwOhzfBx4sP4vYONfHOra2gdY52y5BUINaULwu9tp3tZrv1QK/t9ue2y/G7cuXKxQYWpV6E7LvvvsOAAQOKDCpEaGio2vKTN8sTb1h0uG0N+XSzxa9+IZ7mqfczEOi17Wy3vrDd+mPys7aXdF9KFVgcOXIECxcuxIwZM+BvWNKbiIgowOpYTJw4EVWrVsWgQYPgbxyzQrgIGRERUQAEFhaLRQUWI0eORHBwqUdSPMaxuil7LIiIiAIgsJAhEEnYfOCBB+CPcodC2GNBRETkdS53OfTt2xelnEji3QJZ7LEgIiLyOg0vQsYeCyIiIm/TXGAR6bS6qcXivz0rREREWqS9wMI+FCLSzBwOISIi8ibNBRahwUYYbcuFcIVTIiIiL9NcYCHLuEeG2HotUhhYEBEReZXmAou8RbI4FEJERORNmgwsIkIdRbLYY0FERORNmgwsIu1DISySRURE5F3aDCxyeyw4FEJERORNmu6xYJEsIiIi79JkYBFhT95MYY8FERGRV2kysIiyD4WwjgUREZF3aTKwiMhN3mSPBRERkTdpMrBwrBfC6aZERETepc3AwrF0OpM3iYiIvErTyZtpTN4kIiLyKm0PhbDHgoiIyKu0PRTCHAsiIiKv0niBLA6FEBEReZO2FyHjUAgREZFXaTKwiModCmGPBRERkTdpMrCIYB0LIiIin9BkYBFpz7HIzLYgO8fi690hIiLSDU3nWAiW9SYiIvIeTQYWocFBMAUZ1HUunU5EROQ9mgws8ixExgROIiIir9FsYMGFyIiIiLxPu4EFFyIjIiLyOs0GFlyIjIiIyPs0G1hwITIiIqIACCyOHz+Oe+65B5UqVUJ4eDhat26NjRs3wn8XImOPBRERkbfYjr4ldPHiRXTv3h3XX3895s6diypVqmDfvn2oUKEC/LXHgtNNiYiI/DSwePfddxEbG4uJEyfm3la/fn34c45FCmeFEBER+Wdg8eeff6Jfv34YMmQIli1bhlq1auHxxx/HQw89VOj/yczMVJtDUlKSujSbzWpzF8dzOS7Dg20FspLTs9z6Ov4mf7v1RK9tZ7vZbj3Qa7v9ue0l3R+D1Wq1lvRJw8LC1OXo0aNVcLFhwwY89dRT+OqrrzBy5MgC/8+YMWMwduzYK26fMmUKIiIi4Clzjxow71gQulez4M4GXC+EiIioLNLS0jBs2DAkJiYiJibGPYFFSEgIOnXqhNWrV+fe9uSTT6oAY82aNSXusZDhlHPnzhW5Y6WJpOLi4tCnTx+YTCZ8t+ow3pm3Fze3qYEPh7SGVuVvt57ote1sN9utB3pttz+3XY7flStXLjawcGkopEaNGmjRokWe25o3b47ff/+90P8TGhqqtvzkzfLEG+Z43phw22umZ1v86hfjKZ56PwOBXtvOdusL260/Jj9re0n3xaXppjIjJD4+Ps9te/fuRd26deFvIu0rnHJWCBERkfe4FFg888wzWLt2Ld566y3s379f5Ul8/fXXGDVqFPx1EbIU1rEgIiLyz8Cic+fOmDlzJn755Re0atUKb7zxBsaPH4/hw4fDb3ssON2UiIjIa1zKsRA33nij2vxdpL3HIi2LPRZERETeot21Quw9FiyQRURE5D0aDiwcPRYMLIiIiLxFs4GFI3nTnGNFVjYLZBEREXmDZgMLxyJkgr0WRERE3qHZwCI4yIjQYFvzmGdBRETkHZoNLPLmWXBmCBERkTdoOrCIsA+HpLLHgoiIyCs0HVhE2XssUll9k4iIyCv00WPB5E0iIiKv0HRgwVoWRERE3qXtwIILkREREXmVdgKLSwmoc355npsiuBAZERGRV2kjsMhMQfCELmif8C1wfn/uzZH2HotUTjclIiLyCm0EFqFRsNbtpq4a982/oseC002JiIi8QxuBBQBr437q0uAUWETlLp3OwIKIiMgbNBNYWByBxdF1QNoFdT2CdSyIiIi8SjOBBcrXRVJYbRisOcD+hXkWIuNQCBERkXdoJ7AAcKpce9uV+Ll56liwQBYREZF3aCywaGe7sn8RkJ2FSMd0U84KISIi8gpNBRYXIxrCGlEZyEwEElYjIrdAFnssiIiIvEFTgQUMRlgb9bVdj5+XuwhZGpM3iYiIvEJbgYXT7BDsnYsIk615zLEgIiLyDs0FFtYG1wFBIcDFw4hJPZg7K8Rqtfp614iIiDRPc4EFQqKA+j3U1egjcerSYgUysy0+3jEiIiLt015gIZoOUBch+xfk3sRaFkRERJ6nzcCiSX91YTi2HjVNqeo6q28SERF5njYDi3K1geqtJeMCfUxb1U1M4CQiIvI8bQYWooltOOQGw0Z1yYXIiIiIPE+7gYU9z6JzzlaEwMyhECIiIi/QbmBRox0QVR0RSMfVxl1M3iQiIvIC7QYWRiPQxFYsq5dxM1K5XggREZF/BRZjxoyBwWDIszVr1gz+PhzSO2gz0jLNvt4bIiIizbMtpuGCli1bYuHChZefINjlp/Ce+tchyxCKWjiP0Au75QZf7xEREZGmuRwVSCBRvXp1BISQCByK6YymiStR6/RSAAN9vUdERESa5nJgsW/fPtSsWRNhYWHo2rUr3n77bdSpU6fQx2dmZqrNISkpSV2azWa1uYvjufI/58EK16jAot755W59PX9RWLv1QK9tZ7vZbj3Qa7v9ue0l3R+D1YXVuebOnYuUlBQ0bdoUJ0+exNixY3H8+HHs2LED0dHRheZlyOPymzJlCiIiIuBp6xIS8db5f6nr81p9ikxTeY+/JhERkdakpaVh2LBhSExMRExMjHsCi/wuXbqEunXr4qOPPsKDDz5Y4h6L2NhYnDt3rsgdK00kFRcXhz59+sBkMuXe/uPaBHSMuwPtjAeRPfBjWNuPgJYU1m490Gvb2W62Ww/02m5/brscvytXrlxsYFGmzMvy5cujSZMm2L9/f6GPCQ0NVVt+8mZ54g3L/7zR4SFYlNNBBRbBB+KAqx6AFnnq/QwEem07260vbLf+mPys7SXdlzLVsZBhkQMHDqBGjRrwV1GhwVhk6WD74cASwJzu610iIiLSLJcCi+eeew7Lli3D4cOHsXr1atx6660ICgrC0KFD4a8iQoKwy1oXZ4xVgOx04OAyX+8SERGRZrkUWBw7dkwFEZK8eeedd6JSpUpYu3YtqlSpAn8VGSqjPQasDupku2HvXF/vEhERkWa5lGMxdepUBJrIEFsTF1s64hbMBfbOByRf1WDw9a4RERFpjnbXCrGLDA1SlyvMTQFTJJB8Eji5xde7RUREpEmaDywi7D0Wl8xBsDa83nZjPIdDiIiIPEE3PRYy+pHV0LbaKQMLIiIiz9B8YBFuCspNp0iqIz0WBuDUNiDxuK93jYiISHM0H1jI0u6R9uGQ1KCKQO3Otjv2zvPpfhEREWmR5gMLRy0LkZKZDTTtb7uRwyFERERup4vAQqpvirSsHKCpfen0Q8uBrFTf7hgREZHG6CKwiLAncKZmZQNVmgHl6wI5mbYS30REROQ2+ggsHDkWMhQimZxNB9juYBVOIiIit9LXUEhmju2GJvY8C6nCabH4cM+IiIi0RVfJm2ooRNTtDoTGAKlngeObfLtzREREGqKLwCIyxCl5UwSHAI162a5zOISIiMht9BFY2IdC1HRThyb2PIt41rMgIiJyF50EFrahkDTnwKJxH8BgBM7sBC4e8d3OERERaYi+ZoU4hkLUjRWBOl1t11mFk4iIyC10EVhEOepYOPdYOM8OYRVOIiIit9Bvj4Vw1LM4vBLISPLBnhEREWmLfnMsROXGQMWGgMUMHFjsm50jIiLSEP3OCsnfa8HhECIiojLT1VBIbh2LggKLfQsASwH3ExERUYnpayjEUXnTWezVQFh5IP0CcHS993eOiIhIQ/QRWIQUMRQSFGyraSFYhZOIiKhMdJVjkWG2IMdivfIBnHZKRETkFrpahKzQ4ZBGvQFjMHBuL3D+gHd3joiISEN0EViEBhsRbDSo66mOpdOdhZcH6nazXWcVTiIiolLTRWBhMBiuXDo9v9xFyTgcQkREVFq6CCyc8yzSCuqxEE3teRZHVgPpF724Z0RERNqhu8CiwJkhomIDoEozwJoD7F/k3Z0jIiLSCP0EFvahkAKTNx04O4SIiKhMdBNYFLoQWUFVOPfHATlmL+0ZERGRduhuKOSKpdOd1e4MRFQCMhKBhDXe2zkiIiKNKFNg8c4776gZF08//TQCpax3kYGFMQho3M92PZ7TTomIiLwWWGzYsAH/+9//0KZNGwT8QmQFzQ6R8t7WAqp0EhERkXsDi5SUFAwfPhzffPMNKlSogEBK3iy0joVDwxuAoBDgwkHg3D7v7BwREZFG2E7jXTRq1CgMGjQIvXv3xptvvlnkYzMzM9XmkJSUpC7NZrPa3MXxXIU9Z7jJVnkzOT2r6Nc1hiGobncYDy5Bzu6/YCn/JPxZce3WMr22ne1mu/VAr+3257aXdH8MVqtr/f1Tp07FuHHj1FBIWFgYevbsiXbt2mH8+PEFPn7MmDEYO3bsFbdPmTIFERER8JbFJwz440gQOle24J7GliIfW//sQrQ59iPORzbByib/9do+EhER+au0tDQMGzYMiYmJiImJcU+PxdGjR/HUU08hLi5OBRUl8dJLL2H06NF5eixiY2PRt2/fInesNJGU7FefPn1gMpmuuP/S+qP448hulK9SHQMHtiv6yRJbA5//iIpp+zGw59VAREX4q+LarWV6bTvbzXbrgV7b7c9td4w4FMelwGLTpk04c+YMOnTokHtbTk4Oli9fjs8//1wNeQQFXV5JVISGhqotP3mzPPGGFfa85SJs+5ButhT/upUbANVawXB6B0yHlwBt74a/89T7GQj02na2W1/Ybv0x+VnbS7ovLgUWvXr1wvbt2/Pcdv/996NZs2Z44YUXrggq/Emxi5AVVIXz9A4gfk5ABBZERET+wKXAIjo6Gq1atcpzW2RkJCpVqnTF7QG3CFl+TQcCKz4A9i8GsrOA4BDP7iAREZEG6K7yZqGLkOVXsz0QVQ3ISgaOrPTszhEREel5uqmzpUuXQjOLkDkzGoHGfYF/frJV4ZT6FkRERFQk3fRYRDjWCimu8mZBi5LJaqeswklERFQs3QQWUfaS3lnZFphziq5jkatBTyA4DEhMAM7s8uwOEhERaYBuAotw+1CISwmcIZFA/esu91oQERFRkXQTWIQEGxESZHRtymmeRcm42ikREVFxdBNYlHjp9ILqWYhjG4GUMx7aMyIiIm3QVWDhWDrdpQTOmJpADSkBbgX2zvfczhEREWmALnss0lzpsXCeHcLhECIioiLpLLBwsUhW/uGQA4sBc4YH9oyIiEgb9BVY2IdC0lwZChE12gLRNQFzGnBouUf2jYiISAt0FVi4vBCZg8HgNDuE006JiIgKo6vAIspRfdPVoRDRxJFnMZ9VOImIiAqhq8AiIne6qYtDIaJ+D8AUASQdB05tc//OERERaYCuAovI3ByLUvRYmMKABtfbrrMKJxERUYF0OiukFD0W+RclIyIiIn0HFo7kzVL1WIgm/SSTEzi5BUg66d6dIyIi0gBd9liUKsdCRFUFanW0XWexLCIioivoMrAodY+FcEw75XAIERGRzgMLRx2L0kw3dWg60HZ5aBmQleamPSMiItIGXQUWpVqELL+qLYBydYDsDODgUvftHBERkQboskCWy4uQOWMVTiIiokLpskCWy4uQFbYoWfw8wGJxw54RERFpg64Ci0inRcisZSnLXe8aICQaSD0DnPjHbftHREQU6PQVWNh7LLItVmTllKGnITgUaHSD7TqHQ4iIiPSdvFmmWhb5FyWT4RAiIiLSX2ARZDQgzGQs+5RT0bgvYDACp7cDlxLcs4NEREQBTleBhYh0yrMo2xNVAmK7XF5KnYiIiHQYWOQuRFbGHos8s0OYZ0FERKTLwKLMC5EVtNrp4RVAZnLZn4+IiCjA6S6wKPNCZM4qNwEq1AdysoADi8v+fERERAFOx4GFG3osVBVO+9ohnB1CRESkw8DCnUMhwlHee998wOKGXhAiIiK9BBYTJkxAmzZtEBMTo7auXbti7ty5+luIzFmdrkBoOSDtPHBso3uek4iISA+BRe3atfHOO+9g06ZN2LhxI2644QYMHjwYO3fuRKCIslffdMtQiAgyAY17267Hz3HPcxIREQWoy6UoS+Cmm27K8/O4ceNUL8batWvRsmXLAv9PZmam2hySkpLUpdlsVpu7OJ6ruOcMC7bFUsnpWW57fUPDPgje8Tus8XOR3fO/8KaStluL9Np2tpvt1gO9ttuf217S/TFYS7kaV05ODn799VeMHDkS//zzD1q0aFHg48aMGYOxY8decfuUKVMQEREBb1twzIDZR4PQtaoFdzd0z8qkpuxU9N8+CkZYENfiA6SFVnXL8xIREfmLtLQ0DBs2DImJiSodwm2Bxfbt21VuRUZGBqKiolSAMHCgfWZECXssYmNjce7cuSJ3rDSRVFxcHPr06QOTyVTo435YcwRvzonHoFbVMf6uNm57/aCfB8N4ZBVy+oyD5apH4C0lbbcW6bXtbDfbrQd6bbc/t12O35UrVy42sHBpKEQ0bdoUW7ZsUU/822+/qR6LZcuWFdpjERoaqrb85M3yxBtW3PPGhNv2JT3b4t7Xl2mnR1YhaP98BHV/At7mqfczEOi17Wy3vrDd+mPys7aXdF9cnm4aEhKCRo0aoWPHjnj77bfRtm1bfPLJJwgUEe5O3sxfhfPIaiAj0b3PTUREpJc6FhaLJc9QR8AUyHJXHQuHSg1tlTgt2cD+he59biIiogDh0lDISy+9hAEDBqBOnTpITk5W+RVLly7F/PmBs7pnpGN1U3eU9C5oUbJze21VOFvd7v7nJyIi0lJgcebMGdx77704efIkypUrp4plSVAhCSaBtgiZ23ssHMMhqz8F9i0AcrKBIJdTWIiIiAKaS0e+7777DoEuKtSDPRa1rwLCKwDpF4Gja4F617j/NYiIiPyY7tYKyU3ezMpGKUt4FE56KBr3s12PD6xS50RERO6gu8Ai0p5jYbECGWb3FMgqcFGyvVztlIiI9Ed3gUW4KUitdu6xPIuGvQCjCTi/Hzi3z/3PT0RE5Md0F1gYjQZEmDxUy0KExQD1utuucziEiIh0RneBhYhw1LLwRAKnowqn4HAIERHpjC4Di9yZIZ4YCnHUsxAJa4G0C555DSIiIj+ky8DCUcsixRNDIaJCXaBqC8CawyqcRESkK7oMLCId1TezPDQU4txrET/Hc69BRETkZ/QZWHhqIbKC8iz2LwKyszz3OkRERH5E58mbHgwsanUEIqsAmUlAwmrPvQ4REZEf0WVgEZm7XogHh0KMRqcqnJwdQkRE+qDPwMLTs0LyV+GUPAt3lw8nIiLyQ7pO3vRYHQuHBtcDQaHApSPA2T2efS0iIiI/oMvAInchMk/mWIjQKKB+D9t1VuEkIiId0GVgEemN6aYOXJSMiIh0RNc5Fh4rkFVQPYuj64GUs55/PSIiIh/S9awQjydvinK1geptAFiBfQs8/3pEREQ+pMvAwuOLkOXXdIDtci/zLIiISNt0GVhEOZI3vdFj4Twcsn8xYM7wzmsSERH5gC4DiwhvTTd1qNEOiKoOmFOBwyu985pEREQ+oMvAIjJ3VoiXeiykCmfu7BAOhxARkXbpM7CwD4XIdFOLxUsVMZsMuFzem1U4iYhIo3QaWNh6LESa2UvDIQ2uA4LDgaRjwOkd3nlNIiIiL9NlYBEabITRYLue5o1aFsIUDjToabvOKpxERKRRugwsDAZDbq+FR1c4LWzaKQMLIiLSKF0GFiIyd2aIl3osRBP7MuonNgPJp7z3ukRERF6i28DCawuROYuuDtTsYLu+d773XpeIiMhLdBtYRIV6cSEyZxwOISIiDdNtYBFhXy/EKwuRFRRYHFwKmNO9+9pEREQeptvAItLbRbIcqrUCYmoD2enAwWXefW0iIiIP029g4e2FyBwMBlbhJCIizXIpsHj77bfRuXNnREdHo2rVqrjlllsQHx+PQK6+6dXkzYKqcFos3n99IiIifwgsli1bhlGjRmHt2rWIi4uD2WxG3759kZqaioBdiMzbyZui/rVASBSQcgo4ucX7r09EROQhl2tbl8C8efPy/Dxp0iTVc7Fp0yb06NGjwP+TmZmpNoekpCR1KUGJbO7ieK6SPmd4sK30ZkpGllv3o2SMCKrfE8b4v5GzezYsVVt7rd1aote2s91stx7otd3+3PaS7o/Bai39ilj79+9H48aNsX37drRq1arAx4wZMwZjx4694vYpU6YgIiICvrLouAF/JgShcxUL7mnk/eGI2PMr0CHhG1wKr4tlzd7w+usTERG5Ii0tDcOGDUNiYiJiYmLcH1hYLBbcfPPNuHTpElauXFno4wrqsYiNjcW5c+eK3LHSRFIyPNOnTx+YTKZiHz95/VGM+Ws3+raoii+GtoPXpZ5F8PgWMMAK87+2AjG1vNJuLdFr29lutlsP9Npuf267HL8rV65cbGDh0lCIM8m12LFjR5FBhQgNDVVbfvJmeeINK+nzlosIUZfpZotvfnHlawJ1rgYS1sA0/0Xg7imAsfSTdDz1fgYCvbad7dYXtlt/TH7W9pLuS6mOZE888QT+/vtvLFmyBLVr10Ygyk3e9MWsEId+bwFBobZpp0vf8t1+EBERuYlLgYWMmkhQMXPmTCxevBj169dHoIrMLZDlg1khDrU6ADd9Yru+/H1g1x++2xciIiJvBxYy/PHzzz+rxEupZXHq1Cm1paenB24dC29X3syv3VDg6sdt12c+Bpze6dv9ISIi8lZgMWHCBJW00bNnT9SoUSN3mzZtGgKNzypvFqTPG0D9HoA5FZg6DEi74Os9IiIiKhWXkjfLMDPVbxch82mOhUNQMHDHJOCbnsDFw8BvDwDDf7PdTkREFECMel82PTPbguwcPyirHVnJNjPEFAEcXAIsGuPrPSIiInKZbgMLx6wQn5X1Lkj11sDgL2zXV38GbJvu6z0iIiJyiW4Di5BgI0xBBt8snV6UVrcB14y2Xf/zX8AJriVCRESBQ7eBRd5aFn7SY+Fww3+BRn2A7Axg6nAg5ayv94iIiKhEdB1YOPIs/KrHQhiDgNu/BSo2BJKOAb+OBHL8azEaIiKigug6sHDMDEnxh5kh+YWXB4b+AoREA0dWAfNe8vUeERERFUvfgYWjx8LfhkIcqjQFbvvadn3DN8DmH329R0REREXSdWAR5S/VN4vSbCDQ82Xb9dnPAkc3+HqPiIiICqXrwMJvkzfz6/E80OxGICcLmHYPkHTS13tERERUIF0HFpH2HAu/S97MT5ZTv/UroEpzIOUUMH0EkJ3p670iIiK6gr4DC39aL6Q4odHA3ZOBsHLAsQ3A7NFSY93Xe0VERJQHAwt/z7FwVqkhcMf3gMEI/PMzsOFbX+8RERFRHroOLPxqIbKSatQb6G1fR2TeizDIVFQiIiI/oevA4nKBrAAYCnHW7Umg1R2AJRtBMx5AeNY5X+8RERGRouvAwjErxC8LZBXFYABu/gyo3gaGtPOou/NTHD193td7RUREpO/AIjI0QGaFFCQkQiVzXkIMmuIwUn5/gsmcRETkc/oOLAKljkUhDmdXwqNZTyLbakSbi3GwrPrM17tEREQ6p+vAIsJReTPQhkLslu09i7WWFng9e4T62bDoNWD/Il/vFhER6ZiuA4vIkABN3rRbGn9GXf5i7YPp2dfBYLUAvz0AXDjo610jIiKd0ndgEWh1LJxkmHOw5qAtYbNfbSteyb4f2w2NgYxLwC/DgMwUX+8iERHpkM4Di8AdCll/6AIyzBZUiwnF9TWsCAmLwIPpTyMrvApwdjcw61HAYvH1bhIRkc7oOrBwTDc151iRlR1YB+Gl8WfVZY/GlRFsBPo0r4ozqIAfYt8EgkKA3X8BKz709W4SEZHO6DqwcCxCFohTTpftPZMbWIiBraqpy68PVYZlwAe2By15E4if67udJCIi3dF1YBEcZESonO4HWJGsoxfScOBsKoKMBnRrUFHd1rVBJZQLN+FscibWV7wR6Px/tgf//hBwdq9vd5iIiHRD14GFcwJnIM0MWbrXNgzSsU4FxISb1PWQYCP6tbT1WszedhLo/w5QtzuQlQxMHQqkX/LpPhMRkT4wsAjABM5l9vyK65pWyXP7oDY11eXcHSeRYwgGhvwAxNQGzu8HZjwEWAIneCIiosDEwCLAqm9mZudg9QHbomPXNckbWHRrWAnlI0w4l5KFdYfOA1FVgLt/BoLDgH0LgCXjfLTXRESkF7oPLHKXTg+Q5M2Nhy+qYZsq0aFoWTMmz32mICP6t6x+eThE1GxvW7BMyCyRnTO9vs9ERKQfug8sLudYZAdMGW/Ro3EVGGSV03wGtamhLuftOIXsHPsU2jZ3Al2fsF2f9ThwaocX95iIiPSEgUWADYU4ynj3zJdf4SCzQypEmHA+VYZDLly+o/dYoMH1gDkNmDoMSHO6j4iIyFeBxfLly3HTTTehZs2a6ox51qxZCGSBtBDZiUvp2Hs6BUYDcK29fkVBU2j7t7L1WvztGA4RQcHAHd8DFeoBl44Av94H5Ph/m4mISOOBRWpqKtq2bYsvvvgCWhCVu15ITsAMg7SLLY/yESGFPu7G3OGQk5eHQ0REReDuKYApEji0DIh71fM7TUREumI7qrpgwIABatNaWe+0AOixuDwMUrXIx3WpXxGVIkPUcIgsVHZtY6dhk2otgVsnANPvBdZ+AdRoA7S929O7TkREOuFyYOGqzMxMtTkkJSWpS7PZrDZ3cTyXq88ZFmxLgEzOyHLr/ribOceClftt00yvaVjhivbm3/e+Larilw3H8NeW47i6Xvm8T9Z4IIzdn0XQqg9h/fNJ5JRvAKvMHgkwpf2dBzo9tlt63t6YvRuJpwzoo6N26/X3red2+3PbS7o/BqvVai3ti0iOxcyZM3HLLbcU+pgxY8Zg7NixV9w+ZcoUREREwNeWnjRg5uEgdKhkwcgm/rsQ2f5E4LNdwYgKtuKNTjkqz6Io+xIN+HxXECKCrXizYw6C8g96WS3ocnA8qidtQbqpIpY1HYtMUzlPNoGo1FadNmD6QVs+1KgWOWhSrtRfW0RUSmlpaRg2bBgSExMRE5O33IFXeyxeeukljB49Ok+PRWxsLPr27VvkjpUmkoqLi0OfPn1gMtnKXJdE6qZjmHl4F8pXroaBA/33rP39BbLex2H0alkTNw5qXWy7cyxW/PLeMjUcUr7pVQUne2b0gHVSX4Sf34++iZORM3yGbWXUAFHa33mg01u7kzOy8fr4lQCy1M8LzsXgiTu7qkRlPdDb71vv7fbntjtGHIrj8cAiNDRUbfnJm+WJN8zV540Ot+1bmjnHr36B+a3Yb5seekPzagXuZ/52y7WBrWvgp7VHMG/XGdzQwpbQmfc/VQKGTgW+uQHGo2thXPgKcONHCDSe+iz5O720+7vFB1SAXLdiBM4lpWLfmVRM33wSI7vVg57o5fedn17b7Y9tL+m+6CPkL8FaIf68CNnppAzsPpkEqYeVJxGzGI5iWfN3nkZWdiHDPJUbA7d/KwNbwMbvgE2T3LXbRG6ZYv3tikPq+ov9m2BgrO1z/FHcXlxMtfVgEJF/cTmwSElJwZYtW9QmDh06pK4nJCQgEEXmFsjK9vtFx9rULo+KkSUfquhcr6Iq/Z2YbsYq+/oiBWrSD7jhP7brs58DEtaVeZ+J3OGD+fHIzLbgqvoV0atZFXStZkXTalHqM/1hXLyvd4+I3BFYbNy4Ee3bt1ebkPwJuf7qq68GdElvf6686ahfkX/RseIEGQ0Y2Crf2iGFufY5oMVgwGIGpo8Akk6UfoeJ3GD7sUTM+Oe4uv7fQc1VsniQAXhlUDN125R1Caonj4gCPLDo2bMnZCJJ/m3SpMDsQvf3Rchkmt2KfWeLLONdFMdS6vN3nip8OETIOMvgL4GqLYGU08C0ewBzBvROVpOV7ng5yC3Zcwa/bjyKr5YdwPvz9+DA2RRf755myXfKuDm71PVb29dSvXXOdVoGtq4OixUY+9dO9Vgi8h8eT94MlMqbkmMhX1AFLezlS/8cvYSkjGy1HHpbpy/XkupUtwKqRofiTHImVu4/ixuaVSv8waFRwN2Tga97Asc3AbNHA4O/sAUdGpNhzsH244k4l5yJc6lZ6vJ8aibOp2ThXIrt8mxKppqRUJgFO09jzlPXqlVlyb0W7j6DtQcvIDTYiOf6Nb3i/pcHNsci+2Pm7jilEpW1SE4Gnpm+DVsOBCG43mkMbFPL776jiPLTfWARYQ8sZHqmjOWGmWw9GP6WXyFJmzK04SqjDIe0roFJqw+rtUOKDCxExfrAkEnAz7cBWyYD1dsAVz8KLUnJzMbNn6/EwbOpJXp8sNGASlEhqBwVikpRoagcFYLFe85g35kU/LTmCB64pr7H91lPpBjc23N2q+sPXlMftcqHX/GY2hUi8Mh1DfHpon0YN3s3bmhW1e/+dstKTnRe/H0bZm8/pZKrR/2yFV3XHcOrN7VA8xrum6pP5G4MLJy+jCSB09++nJbutZfxdjG/Iv/sEAks4naeVl37ocHFtLHh9UCfN4AF/wHmvwxUawHU7wF3u5CapQp9FbXuiSd8tGCvCiqiQ4PRpHq0Kn8uAUOVKNulLYCwBRISRJQLN11xljh53RH8Z+YOfLxwL25uV1M9ltzjl/UJOHguVf1eHuvZsNDHPXpdAzU0dfxSOv637CCe6t0YWvL+/HiVYyInFJ0q5eCfi8GqRP+gT1fgrs518GzfJvzckV/SfR+unNE78iz8bcrp2eRM7DhuS07rUYbAomOdCqgWE4rkzGys2FvE7BBnXUcBbe4CrDnA9JHAxSNwp2MX03DDh0vR+6NlOJPkvVwOyZWYtNo2ffHz4R3w+2Pd8PW9nfD2ba0xum9TVRtBArGrG1RCo6pRKugpqOv57s510KpWjBoqeX8eZye4S1KGGeMX7lPXn+7TBNFhpiLX+XlpYHN1fcKy/SrA0AqpP/Pl0gPq+puDW2BYIwvmP9kdg1rXULklEnxd//5SfLP8YNG5U0Q+oPvAwnkhMuki9yfL7bNB5AAm00ZLyzEcImZvL2Z2iIMcTG/6BKjRDki/AEwdDmSlwR0sFiue+3UrLqWZcS4lCy/N2O6VBDxJhH1p5jb1xXxz25ouz7JxJmeRY25qqa5P33QUW49ecuOe6tcXS/arniwJ6oZ2ji328Te1qYGr6lVEhtmCd+bugRYs2HkKr/2xQ11/pncT3NGhlrpeu0I4vhjeAdMf6WoLajOzMW7ObvT9eBnidp1mEiv5DQYWeYpk+VdgsdQeWPRsUvRqpiXhWEpdvoAkcbFETOG2ZM6IysDp7cAfo2Tgt8z78v2qQyrpLtwUhJAgIxap2RbH4Gk/rDmieoBiwoLxyo0tyvx8nepVVDMW5C159c+dKmCi0jt6IQ0TVx1W118e2KxEJbulN0lyDiQO/mvrCaw/ZKtQG6g2J1zEk1P/UcHv0Kti8WSvRlc8Rmp6/DnqGrx3Rxt1wnH4fBoe+nEjRny3HntOcfot+R4DizxFsvxnKESSScsyzTS/9rEVUKNcmOqVcfSElEi52sBdPwHGYGDnDGDVJ2Xaj72nk/HefNvQwX9vbI7RfZuo66//vUsNj3iKdJN/uMD2utJ9XpYeIGcvDWiGyJAg1WPx+2bPB0daJjkF0q3frWElXN+05MF0q1rl1NCUGPPnTvW3E4gOnk3Bg5M2qN4XSUZ9Y3CrQmeASC/knZ1iseS5nni8Z0OEBBvV6scDP1mB/87ajvMpl1eUJn2Zv/MUJq2yDff6CgMLpx4Lf6q+ufXYJTVUIGfX7WJdn2bqluEQh7rdgAHv2q4vHAPsW1j6qXPTtqhLCZaGXVUHD13bAB3rVlABz79/2+aRs37pIpauZcmhkem3d3Uqvou9pKrGhOHJXrakwXfn7VE5AuS6LUcv4c+tJ1TPw3/sxbBc8VxfyccIxq6TSZi24SgCjeRTjZy4HhfTzGhbuxw+H9a+RD02Ml3+3/2bYdHo6zCgla22x89rE9Dzg6X4dgXzL/Rm+sajeOznTRjz1y6s2l/CfDoP0P2sEOcci1Q/St5c6jTN1F2rOEpS4ncrD2GhfTjEpRkwnR4ETm4FNv8I/PYA0KSvrRdDtiCT/boJCAq+fF3dd/n6svjzaHX6IjqGm/Bsi5Yw7DyKIKMJX3bKwSsn9iD1kAEL5hxD/za1C3iuIPvrOD2vBTBasgBLDmANLrTehqyVInURTEEGvHVbaxVkudP93eurg5nMZPh04T781w3DLLorhjXbVgzr9g610bJmOZefQ2bzPN27Cd74exc+WBCvkhzLRfjP4k1FkROaByZtwNEL6ahTMQLf3dc59zuppGIrRmDCPR2x9uB5vP7XLhVgvTl7t6pOKoGa9ICw/oW2fbP8oMq5EUM61laF5HyFgUWeIlnZ/lfG2w3DIA7tY8urmgAyLCDP36+lrdx3iciX0sAPgDN7gGPrge2/uvz6fWST73rplJh7+XaprPG1xDiybbRvJSBPdZNc2Wq/QQUjpjwBj8UQhNbJOVgUYkRkdDlUn1sFCIkCQqNtBcHUZTQQEp3vtpi8j5PrEtwUQLqhZZz/vokb1LTeu6+KRaOq0S6/P3ruut1w+CLCTEY81/fKYlgldW/Xumq2xP4zKRi/aC9esyfX+jNJKH5iymZVrE3WAfrhgavKNIVUZjP99a9r8Numo2poSYLdB3/YqAK2d29vrZul5v0hWAwyGrxSvkACcxlenmCfRfTQtfVVATlfBpIMLJzKevvLrBAZH912zDbLoCwzF/KTD5qUQv5mxSG1dohLgYUIDgVGzAR2zQIyEgFLNpBjtl0WcT3bnInle04hMysTseVMaFU90rYmifQ0qMeZYbVk4+i5JGRlZkJONGtEBcGQ57lybP9HrssU2IKo58w7FCFfo7UcV1JPAqllmDkgwcUVQYktAOkZGo0vq6VgxzkLVk5ZhYY9W8Ig9xUUqIREarKaaWlIV71jNsfD1zZA9XJhpX4uqYD66o0tcO/36/HjmiNqqK1xNf8N8OSAILVQlsSfVUHVdyM7oX7lyDI/rxzQpM6FDH1+seQAvllxUOX/SC/l+LvbsVKsh83edlIVNpMTDkmw7dW8mKKEZSD5RJJT88t62/Dfv/s3xWPXNfR57xQDC6eFyNL8JHlTkrBkpoFU16sWU/ov2sLWDpHAYuHuUgyHCDlQtr/Hpf8ydtYO/JR6BNVjwjD/8R5SleyKx8ifQdCldNzy8XKkpGbjhWubFV4cSd4cSzbMmWlYMG8u+va6Xg1zOIIUR8ASf+ICnpu+CcHWHIwd1ARtqpmAzCQgMxnITLFdZqUUc1uyLbgRcrtsKVIJ8UoDZZOmSUw4q6h3xODUU5IvUMntObHfFhwOBIcAQaG2wC4oBAYEoUrSDhgSKgChkXnvtz/GdilbsN/Xa5BZDXKWLpU0y8RqRY8GMRjUNBqr44/js1nL8cmQFjDI5yInE8jJArKzbJfquv022QxGp/fd8XuRwDDa9l564Iv6k0X7MG3jUVUk7vOhHdC+TgW3Pr/UAHlxQDO0r1Ne9YpIblVWjkXlbxRbJM8NEs6n4d35e9R0YOlN8vXBztPk+/TN2btUjouSCdVbJG2XHgR3915IsUPJWZuz/ZT6eI67pTWGdbElMfuaf3/reDt500+GQhz5Fe6YDZKfJIY5hkOWxp9B/1aeXWNBhlzk4CE+GNK2yHFv2S8ZUpAkzo/j9uL6ZlXQrHoBpYvlr0iGPEwRyA4KByIqAibTFV3Mz/yyCrssDWyLWF3TrnQNkCBGDkDFBiC227YfPIaEU6dR2ZSFzjVMMDqCEcfjrJJMZ7X/nFTqP9pucuXAe8U/WA6YKujIG5wUfOn8uPzBSiH3FfQc0r78B251QHc+uGciIyMD2Svi8UJwFq6vXQ6RcXPyHfTtAYH9elB2BnpePI/ghNftjzPnfQ3ZpBaG/CPxuOQof4qykyE2556nK4JCx+3OP+cfTrNv9uG0aRsScguBvXFLK/Ru4bmzWumZlCJwj/60SU03f/jHTfjfiI5lP9BZLEB2Rt5NFi7MzsCOhDP4fMF2mDPTsX6HGeG7o3Fbm8oqyC+s1y+Qe/NkRs+oKf+o1XYNBqheA+mN+3blIdV7tubAeXxyd3u0qBnjtqGWR3/ehBX7zqmTKnluf1ovh4GFU/KmP/RYyKwIx3RQdw6DOMhZg9S0+N/yg2rtEE8GFpfSsvD8r7YEiPu61cM1jSsX+38k6UgKBEmy5bPTt2Lm491Vl6KrpB6CJLBJOW5JXis1+ZYwhdm2yOL3v2FWNh7+cBlOJmbgmfpN8paZliDFnF5ET4m9h8T5tuz0ywdl+0HUYs5AyqXziJaqoLkH18zLj1PBi+M1Lfbn8L+qlHLsf0SuyJ+flK+wlbAolHwKVFpnCQu1ZluNMBtMCAsLgyE3EDI5BUdOgZL0Ssl7rn4v9t+BXBfSC5Z+0baVlSkCmUGR6JwejD9CwlG+fEXUPVgNOJ6vp8opgDEEhaN82kHg/H4gsqLtdqkxI59N+Uypz4DzAT7T9jmTS/W7z8T1lgz8ed1Z/LgiHsYDGfjts+m4s10VhFizLv8f+2Mv/9/8AUO+2+yBXEFaAfhKrjiq9UtPfUkm60ggrHrtohAcEoVr07IRlDjxyqCuwFyofJv8vr3gjy3H8fKM7Sr5X8rQf3xXu9xKyXL57K9b1bpCt3yxSg1VPNC9fpkSyC+mZuG+SRvUFHcZxpcgUZL8/QkDC1XHwp5j4Qc9FjtOJOJ8apZKKJVpmJ4wyB5YyOqQ6Vk5CLe3391e+WOnWlW1QZVIvNC/WYkDH5m5sfHj5dh5IgmfL9mP0X1stS5KSuphfBS3N7fQkjfXU5AgVbo9//XLP/hy6X7c3rGWWjBLkQNBSIRtUymrpZNjNmPJnDkYOHAgTPl6amwPyM4TiNgOBI7gJG+Qkvcys4jHZRTzf52eQ4Z6HL0YuT0ZjgO67XpKdhBm7z6PTEswrm9ZG7FVyl9+vHMviNP1bBixftNWXNXtGgTLe+joOZHnzh36sV1PyTbiho9WqM/fv3s2xeM9ryw0VSwZUlOBhlOwIcGgc/BR2JYnSEy+fBA2pyHUnIYGclyRLekgkFT8l/R1ciV+zJU9UfkDySJIWuw4ic5kk9dcDvcxBsMaHIa0nGBcyg5CptWE0PBI1KhUDonmIOw4k4V0SzDCw0LRuWYownJSr3yvpB2yZSaqTd4eNa/hoC0p0WXy2ck/rFVo0nbBQV1u0FJA4rYMfUjdlKn26c1XN6ioeg6qOQ1fS2Ax76lr8cLv29TJkszUWb7vHD64o42aqu6qU4kZGPHdOhWoyIrXE+/r7PYhNHdgYOGUY7HjeCLiTyWjafVonw+DdG9UyWNJVq1rlUNsxXA1vW1J/BmPdKFJTQKphCiJZB/f2c6l4KVqdBjevKUVnpjyjyrx3Lt5VbQp4ZLxkhD36h87kW7OURUKpYiQt0mPkCxSJtVF35qzG18O7+jdHZC8CtmkW9lPvTB5M2ZnncS1jSvj3qFdSvR/rGYzzu7LgbVOtyuGvvKLMkEFs3K2+Pni/WpWhMv5SnIwCStn28oqOxPHTp3BqInLYE5LRLfaIXjhhtowZade2XPlCGDsvSbWjCRkJJ5FmNEMgzxGhpocPVH5BYfZh6/C7Zf23rbgy9tFsxErD6cgJTsY0VFR6N2mLsLCIpwe5/T/TU7PU+Bttu1ChkXVT1h36IKKn5/vdzmJUA575Y5dwjOTNuJcciaqnwzD9/d1zjssoHrz0pzehyRkp13EptXL0LF1UwRnp9nfk5SiAzi532xftViCubTztq2srnoEGHh56FFmHo2avBnxp5NVe/91Q2M81atxgStQV4oKxTf3dsLP6xLw5t+7VI90/09W4H0XEztluEWqq8owtuSr/fTgVX6bnMzAAlAHLcnKPnI+DQM/XYF7utTBM32aeH3VTSF5D6KnC5UHXSV/7INa18RXyw6oDGZ3BxYSVf935nZ1/YnrG6FtKQp83dimJubtOKWGa0ZP34q//3VNicaE5+44pZY0VzUrbi28cqEnyWuOubmlqoIoiVVSqKZ7o+KHUUoyTDZlfQI+WbgX1UxGtOiSisbVy148zds2HbmgEgnlO7hMw1TFkNwaye+R4lvvzt2Dj+4qZZ6NG1zINGDE1AM4lFoBLWrUxdP/1xUm+wlNcbLNZixw9FAFBV0+AEtvhdPBvaRJpnKgr388UZ35XrxkRot9MeogJQfA0pC8Aikpfuxiuur9lbP2/Dkj8h078/FuuH/SBnVQHvLVanx5T8fLw72qNy/StkVfDiRP7UqBtfXAYgPJK3rsHL1Kub1LBQRvBQUleR6bfHmWmQRddr9vOob/ztqhTl6kN/STu9sV+/dtMBgw4uq66NqgIv71yxb1nklip9wmPZzFnXjJSe/I79er3myZOSS/r9yeUD/EwEK6CKtHI+6Z6zBu9m7M23lKrSkhZ9yy2qVMWSsoCvVUToJ8CXoqvyL/WbUEFov2nFb1O1wtyFNUj8Hzv21FUkY22tQuhyduKEUXtJ2UNJYzIPkiknLc/xlUdOEpqXopXZNCzpZ8WUtCkk7lS0M+S7JPc566tkw9UPtOJ+PFGdux6YhtnP8sjBj42Wo83KMBRl3fyG2/P0+Tz4d0B4shHWMLTs51ExnHlgBPxrZl+fF7utZFBy92GydnmFWuzYlL6WoGyKFzqSpBedL9nXNr57jMKLNXpHs+qkz7JmXQpz7cFcO/Xadyke7+ei0mP9RF9Ra6WoNEZiZIVVsp7vXtyE5oUshZtBTx+v3Rbnjk542qN0+KgknP5NCr3DyTQXrrwsvbtrKS4T0JMIzB6nvytT924tdNx3J7lSWfwpX3rFHVaMwa1U2tiCyJnRL4rjl4Hp8Wkdi57uB5/N8PG9Wicy1rxpS51ok3cEKz04f+qxEdMfn/uqBJtShVWveVWTtw42cr1S/WGyTDV0ryyuvXLB/u0deSD2jdShFqXQI5w3eXn9ceUe0IDTbiozvLNme+QmQI3rmttbouf4TFLTD1wfx4NaYuEf3j15c+oHGX0X2aqqJHMh7605rSLTsv47iSLyI9aRJUyBnhc30ao1k5C8w5VlWnoNeHy/D3thMBsbql9FT8k2BLOnvWvk6MJ0k5/Ds61lbXx7pxHRHJTTpwNgUr951TZZQ/WbhP1S6QGhqy2mjr1+aj9ZgF6PvxclU4TdosicQ/PNC5VGPrnjqhmvbI1apbXT6jd/9vLU4mlizJVz5rny3ah0d+2qSCClnf5Y9R3QsNKhxkVtiPD3TBbe1rqd+FrGz83rw9/ruAn/QCRVbG3qQgDP58lQoq5DxT8r6kHa4GYkKm+kp13h8fuEqtWSQnThL8Sgn2/O+DLNAonykJKmRo95eHr/b7oEIExmmOF0mX1pwnr8XkdQnqLFm6rO76eq1KeJQuKznj8Pw0U88Ng+QdDqmBL5ceUMGAzDUv6xeenJE5SsrK/HlZ+rqsZAzyzk61MX3jMbXU+tynrs3NiXH2T8LF3Gmt425p5ZWKd8WRL1EZa5Yvz48X7sXN7Wq69KUgAe1LM7fj4FnbmLHkmrw+uBWqRAajdvJuhNTvgHFz49WYq+SjTG6QgLGDWxb75e4rMu9e1lMRj/Ro6LUD7L/7NcXc7Sex9VgiGr48B8FGg5ppJEGvXMoKu7afL98uW6jjMfJzsBFpmdk4kZihDr6yjk9JSDAhi//J2byMw/tbRdaGVaLUMuxDv1mrqnTe+b81mPJ/V6sTrcLImfvzv27LXXNIZnzJkFZJTyLkPf7wzrbqNaQnR76Djl5MVzkH/vB3mz+AkpWXX/1zhzoJqxotQx/t0bVhpTI/d48mVTD/6R5qer3UFZKePJme/+GQtqgQHoT1Zw2Yum6rCsB6N6+m6o/42/tTGAYWBZCytyO71cNNbWuq4ELKBEsuwqLdp/HodQ3V5u5fsESqjjLePT08DOIg7Zuw7IDqluz6zmL1ukM6xap1BVyd4qnqRkzbov74pItwZNd6bttPWeJ81f7zSLiQhrfn7sabt9h6MRzMORZ18JYT9ts61EI3N+QzuIskj0oipyzXLt2f797Rptj/k5hmVu10ZJvLWc3Ym1uqRaYkIDSbzWpIuk+LqrihRXVVyleGtaRLdcAnK9R7/3SfxogJ8/xaGfK5Tc7IxsW0LLXJAfdC6uXrzpeSeyMJw9ViQvFQj/rwFglgJNCVpe1VWRKLFdlqXaCyTS+X3qMa5cNV4FCzXDhqlL98WUMuy4UVGAT7mzqVIjD90a4Y9s1alWemgouHri6wCqgEsQ//uFHN2JJATALd0gxlyOdY8tgkuJCeHkn0PpWYjq9HdFI9lf5AakVILsXMf46rnyXRWIY+3NljUDEyBN/c21GdyEpxLentlcTOga2qYfJ+OcZY1Xfae7e3Cahy7P7/qfch+aWPu7U1hnepizF/7VRd8VLURiJY6b2Q8tjuSg6Ucc5zKZmqi7hjPe+MA0tlz6/u6Yivlx9U3ezS7SabzMW+pX0tdVAs6QwZObhJfoisMPn+HW3dutCXVBCU0rgyHixV7fq2qJ47T1xMWnMEe04lo0KECf8tJg/D2yQ/Z+zNrXD7hNWYvumoqoxXWDKrnB3JWeCYP3epz4KQL+0X+zcrtLCYBLjyBS3d/bIA14Jdp/H9qkP4c+txvDiguepyLuvvQvZLuv2lyM/6wxdx8lI6LtgDBskLcrUXWw7y3s4JGdG1Hu7oGKvOtmUISYoXSRVKuZTAVK6bsy3ItF+qn+33Z9kfH24KyhM8RIcGa6aapPTESs+FBBcHzjp6LrrkmXUgSbcy9HEuJUt9R8iiZ9I9XxbyuZUATIo9yXoxt01YrXJQ6lYq3YwmObvfdyYZWxIuqRWiJdfLaDCo4Qu5VLN87T8bHLfl+1k2sXzfWdVbKH/DMvQheVvuXsBQyOvfc3VdNV3Vkdj58zrbScV9Xevg1ZtaeeR1PYmBRQlIUs20h69WX/pvzd6tovZRUzarD4IsdCQH6LJy9FZ0a1jZK+V2navyySbjfL9tOqbWFJAlnGUVVNmkUucdnWJxc9uaqlu3sIxl6dIUrw9u6ZH8EBmiGtnVlgwpXYfzn+mBiGDgfAbw6UbbPHcJ9iQY9DdSj0TOOmZsPq7Ommc+1u2KLwr5TL06a4cK7ETDKpF4+7Y2Jf7iljM/qa4onyPJJZBubRk6mrLuiDqrlGQ9VwIJGdaSHhAJJqRHyxHoFHX2LrOoKkSaUCEixHY9wqQuK0aY1FmoXK9ZLsxnU+Qk895TNVu0QKbjTnukK+75dp0K1GUI+OcHu6jvv+kbjuI/s7aroEy+7+Qs212zEuRv+/fHuuH+iRvU5+7WL1er6ZltahY/lCrfVXJCs+XoRZXHIkWj3LlKteSffDasPTrX8/xKoY3siZ2SKya9lddVzcTLA5oGXFAhGFi4VLGyJno1q6aGD/5nH0IY9OkK1aMhEW1ZuvCWebCMd0lIPoScST7Xt4k6OElCmhTQknFp2WT+df9W1VUmvyRqOT7sklwoQyDSvSxd9be0U0t+ecQLA5qpfZO1Jcb+tRPv3NICvx4yquEXCfIcSXr+SHodFuw8rb74JHiTISfHGdYPqw+rpb4lCU66l6WY0+PXNyxVgCmzieY93UP1Wny6aB82J1zCTZ+vVD0fz/dtWuBnVAIJGWaSIEKCCVl6+3RS3kBC8g0kQJLVMxtXjcoXRJi8GgyT50g3/y8PXa0SBmXFVcm9kPF9+cwK+RuX/Ah39zhJXpBMR33ghw1q2FB6Tj64o/UVOTq7TiSpAOIfezAhQ2sFBbnSKyiJu5ITIR1q0qsmn3OL1Wpbakhtcr2An9XjrWoY6+7Odbx6shIaHKRmvz3fpxHmzp0bsD1iDCxcJGc8EkRIQqEUP5I6BZI0OGPzMXV2KV+8ssmsi5KOiSWmm7Ep4aJXppkWR/ZZEiZlk7PUWf8cV0M/Ugjmjy0n1CbdpnIQl02WCZeMcvlCkmEjT/4hyJeZfKkN+WqNOvuXd3f3JVvSnadf2x3j/E/2aoS35uxRCYz9WlXHsQvpeGnGNhW4iU51K+Dt21qX+Yxe8mMkD0iCPPmMytTpKesSMGf7SbUsuQQZMgXSEUSsPXBeJSXmeY4go1q8Sj7LkqgmX9KBkjhGZSPB58//1wX3T1yvAlNHUPFM7yb41w2NPHYGLX8j0x7uqqrWyky1J6dtxfU1jNg8Zw+2HktSQYUMUTmTP3kJdNvHVkC7OuXVZ7Zx1WivlQjwFIMff5eVBAOLUpJuQKmouPrAObz+1y7VdSjLH8smZJ56p3q2M7wu9SuqrujCsqZX7z+nzlyl+7uobGxvk2Dh/65tgAevqa/OXqQXQwIL6baXoQ/H8Id4747WXonsO9atiId7NFTJir9ttiVVPdajgcpu93f3dauvujhl3Hb4N7b6AfJ7l7H6Fwc2w9DOddz6pS1LkH86tL3K65BaGvIZlWQ0KRYl09ecSXAmwYMKJBpUQoe6FRhI6JgMe/74YBc8Pnmz6mV79/bWHl+wUEgvwdcjOmLsX7vUCdviE0bgREKevLf29t4IKWXdJracV5KUyTUMLMpIciJkeqocJNTZ38ELWH/ovEoakumjjimk0j3XsZ70aNh6NVo7BRrenGZa2uhZKufJJsmRUhRHejFkeXcx9CqZSeK51Rnze6ZPYyzecxp7T6egapgVD3txhkFZexIkJ0cq6Emg5uhaliJOLpebdoF83qRyqUwr/jBur5rFIVMupYCZo0dChjkCpcgWeYecHEmtBZnx5c0ZCfJakqvVqEoEpizfiaua1UXHepVUb4RM2w30s3k94DeJG8hZpvRIyCZn+HIWuueUBBoXVC0CqR4pwx1SI96xcqma/WEfs5b1OvxhGKQk5Cx2cLtaapPFviRx05V69+4ah5SM9PFx8WiOY2r8P1DI71jm/UsQKkNqfVtW99qX9X3d66vZPjLDQypeBsJUSPI9X0xzlOBh2FWxKH9uOwYObF7wYnvkt/jN4gEyvteyZjm1yTCCzPWXbuh1h2xj2hJoyFQ9mbMsm5C1Sso6dcsXw0G+qlcvQx8fDWmDOXNs47+BRHoofEWSLmU4iYjIUxhYeKlHQ6ZsyXZ/d1ugsfdMskqak16Nbccu4dYOtTimTUREAY+BhY8CDemKlk26p4mIiLSiVINnX3zxBerVq4ewsDB06dIF69evd/+eERERkfYDi2nTpmH06NF47bXXsHnzZrRt2xb9+vXDmTPuWyGTiIiIdBJYfPTRR3jooYdw//33o0WLFvjqq68QERGB77//3jN7SERERNrMscjKysKmTZvw0ksv5d5mNBrRu3dvrFmzpsD/k5mZqTaHpKQkdSkrNMrmLo7ncudzBgK9tlvPbWe72W490Gu7/bntJd0fg1WKo5fQiRMnUKtWLaxevRpdu3bNvf3f//43li1bhnXr1l3xf8aMGYOxY8decfuUKVNUTwcRERH5v7S0NAwbNgyJiYmIiYnx3awQ6d2QnAznHovY2Fj07du3yB0rTSQVFxeHPn366KqYil7bree2s91stx7otd3+3HbHiENxXAosKleujKCgIJw+fTrP7fJz9eoFVxAMDQ1VW37yZnniDfPU8/o7vbZbz21nu/WF7dYfk5+1vaT74lLyZkhICDp27IhFixbl3maxWNTPzkMjREREpE8uD4XIsMbIkSPRqVMnXHXVVRg/fjxSU1PVLBEiIiLSN5cDi7vuugtnz57Fq6++ilOnTqFdu3aYN28eqlXz7kJURERE5H9Klbz5xBNPqI2IiIjIWeCsN01ERER+j4EFERERuQ0DCyIiIgrcZdMdhT5LWmjDlYIiUhVMntef5v16ml7bree2s91stx7otd3+3HbHcbu4gt1eDyySk5PVpVTfJCIiosAix/Fy5cq5Z60Qd5CCWrLmSHR0NAwGg9ue11Eq/OjRo24tFe7v9NpuPbed7Wa79UCv7fbntku4IEFFzZo11QKkftNjITtTu3Ztjz2//BL86RfhLXptt57bznbrC9utPzF+2PaieiocmLxJREREbsPAgoiIiNxGM4GFrKD62muvFbiSqpbptd16bjvbzXbrgV7brYW2ez15k4iIiLRLMz0WRERE5HsMLIiIiMhtGFgQERGR2zCwICIiIrfRTGDxxRdfoF69eggLC0OXLl2wfv16BIq3334bnTt3VtVIq1atiltuuQXx8fF5HpORkYFRo0ahUqVKiIqKwu23347Tp0/neUxCQgIGDRqEiIgI9TzPP/88srOz8zxm6dKl6NChg8o2btSoESZNmgR/8c4776hqrE8//bTm2338+HHcc889ql3h4eFo3bo1Nm7cmHu/5FS/+uqrqFGjhrq/d+/e2LdvX57nuHDhAoYPH64K6JQvXx4PPvggUlJS8jxm27ZtuPbaa9XfhVTye++99+ArOTk5eOWVV1C/fn3VpoYNG+KNN97Is+6AVtq9fPly3HTTTapCoXymZ82aled+b7bz119/RbNmzdRj5HM2Z84cn7Rb1r944YUX1D5ERkaqx9x7772qErOW253fo48+qh4zfvz4gG93oawaMHXqVGtISIj1+++/t+7cudP60EMPWcuXL289ffq0NRD069fPOnHiROuOHTusW7ZssQ4cONBap04da0pKSu5jHn30UWtsbKx10aJF1o0bN1qvvvpqa7du3XLvz87OtrZq1crau3dv6z///GOdM2eOtXLlytaXXnop9zEHDx60RkREWEePHm3dtWuX9bPPPrMGBQVZ582bZ/W19evXW+vVq2dt06aN9amnntJ0uy9cuGCtW7eu9b777rOuW7dO7d/8+fOt+/fvz33MO++8Yy1Xrpx11qxZ1q1bt1pvvvlma/369a3p6em5j+nfv7+1bdu21rVr11pXrFhhbdSokXXo0KG59ycmJlqrVatmHT58uPps/fLLL9bw8HDr//73P6svjBs3zlqpUiXr33//bT106JD1119/tUZFRVk/+eQTzbVbPof/+c9/rDNmzJCoyTpz5sw893urnatWrVKf9ffee0999v/73/9aTSaTdfv27V5v96VLl9Tf6bRp06x79uyxrlmzxnrVVVdZO3bsmOc5tNZuZ3K/tK1mzZrWjz/+OODbXRhNBBby4Rw1alTuzzk5OeoX9/bbb1sD0ZkzZ9SHc9myZbl/kPLhkC9ih927d6vHyB+n44NtNBqtp06dyn3MhAkTrDExMdbMzEz187///W9ry5Yt87zWXXfdpQIbX0pOTrY2btzYGhcXZ73uuutyAwuttvuFF16wXnPNNYXeb7FYrNWrV7e+//77ubfJexEaGqq+TIR8acj7sGHDhtzHzJ0712owGKzHjx9XP3/55ZfWChUq5L4Pjtdu2rSp1RcGDRpkfeCBB/Lcdtttt6kvSi23O/+BxpvtvPPOO9X77qxLly7WRx55xOppRR1gnU8o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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 21
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 测试集"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T03:09:48.527250Z",
     "start_time": "2025-01-17T03:09:48.476008Z"
    }
   },
   "source": [
    "model.eval()\n",
    "loss = evaluating(model, val_loader, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\")"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.4706\n"
     ]
    }
   ],
   "execution_count": 22
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "pytorch",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.8"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
